﻿using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Documents;
using System.Windows.Input;

namespace Leow.SelectableCanvas
{
    public class SelectableCanvas : MultiSelector
    {
        static SelectableCanvas()
        {
            DefaultStyleKeyProperty.OverrideMetadata(
                typeof(SelectableCanvas),
                new FrameworkPropertyMetadata(typeof(SelectableCanvas)));
        }

        public SelectableCanvas()
        {
            Padding = new Thickness(
                DragAndResizeAdorner.ThumbSize.Width,
                DragAndResizeAdorner.ThumbSize.Height,
                DragAndResizeAdorner.ThumbSize.Width,
                DragAndResizeAdorner.ThumbSize.Height);
            IsTabStop = false;
        }

        protected override void OnSelectionChanged(SelectionChangedEventArgs e)
        {
            var adornerLayer = AdornerLayer.GetAdornerLayer(this);

            foreach (FrameworkElement elem in e.RemovedItems)
            {
                var adorners = adornerLayer.GetAdorners(elem)
                    .OfType<DragAndResizeAdorner>();
                foreach (DragAndResizeAdorner adorner in adorners)
                {
                    adorner.PositionChanged
                        -= ItemAdornerPositionChangedEventHandler;
                    adorner.PreviewSizeChanged
                        -= ItemAdornerPreviewSizeChangedEventHandler;
                    adornerLayer.Remove(adorner);
                }
            }

            foreach (FrameworkElement elem in e.AddedItems)
            {
                var adorner = new DragAndResizeAdorner(elem);
                adorner.PositionChanged
                    += ItemAdornerPositionChangedEventHandler;
                adorner.PreviewSizeChanged
                    += ItemAdornerPreviewSizeChangedEventHandler;
                adornerLayer.Add(adorner);
            }
        }

        protected override void PrepareContainerForItemOverride(
            DependencyObject element,
            object item)
        {
            base.PrepareContainerForItemOverride(element, item);

            var containedElement = item as UIElement;
            if (containedElement != null)
            {
                if (Canvas.GetLeft(containedElement).Equals(double.NaN))
                {
                    Canvas.SetLeft(containedElement, 0);
                }

                if (Canvas.GetTop(containedElement).Equals(double.NaN))
                {
                    Canvas.SetTop(containedElement, 0);
                }

                containedElement.Focusable = true;

                containedElement.GotFocus += ItemGotFocusEventHandler;
                containedElement.PreviewMouseDown
                    += ItemPreviewMouseDownEventHandler;
            }
        }

        private void ItemAdornerPositionChangedEventHandler(
            object sender,
            CustomPositionChangedEventArgs e)
        {
            var adorner = sender as DragAndResizeAdorner;
            if (adorner != null)
            {
                var otherSelectedElements = SelectedItems
                    .Cast<FrameworkElement>()
                    .Where(elem => elem != adorner.AdornedElement);
                foreach (FrameworkElement elem in otherSelectedElements)
                {
                    Canvas.SetLeft(elem, Canvas.GetLeft(elem) + e.XOffset);
                    Canvas.SetTop(elem, Canvas.GetTop(elem) + e.YOffset);
                }
            }
        }

        private void ItemAdornerPreviewSizeChangedEventHandler(
            object sender,
            CustomSizeChangedEventArgs e)
        {
            var adorner = sender as DragAndResizeAdorner;
            if (adorner != null)
            {
                var otherSelectedElements = SelectedItems
                    .Cast<FrameworkElement>()
                    .Where(elem => elem != adorner.AdornedElement)
                    .ToList();

                foreach (FrameworkElement elem in otherSelectedElements)
                {
                    double newWidth = elem.ActualWidth + e.WidthDifference;
                    if (newWidth < elem.GetActualMinWidth()
                        || newWidth > elem.MaxWidth)
                    {
                        e.Handled = true;
                        return;
                    }

                    double newHeight = elem.ActualHeight + e.HeightDifference;
                    if (newHeight < elem.GetActualMinHeight()
                        || newHeight > elem.MaxHeight)
                    {
                        e.Handled = true;
                        return;
                    }
                }

                foreach (FrameworkElement elem in otherSelectedElements)
                {
                    elem
                        .EnsureElementHasSize();

                    elem.Width += e.WidthDifference;
                    elem.Height += e.HeightDifference;
                }
            }
        }

        private void ItemGotFocusEventHandler(object sender, RoutedEventArgs e)
        {
            var element = sender as UIElement;
            if (element != null)
            {
                if (Mouse.LeftButton == MouseButtonState.Pressed
                    && (Keyboard.Modifiers.HasFlag(ModifierKeys.Shift)
                        || Keyboard.Modifiers.HasFlag(ModifierKeys.Control)))
                {
                    if (!GetIsSelected(element))
                    {
                        SelectedItems.Add(element);
                    }
                }
                else
                {
                    SelectedItems.Clear();
                    SelectedItems.Add(element);
                }
            }
        }

        private void ItemPreviewMouseDownEventHandler(
            object sender,
            MouseButtonEventArgs e)
        {
            var element = sender as UIElement;
            if (element != null)
            {
                if (e.ChangedButton == MouseButton.Left
                    && Keyboard.Modifiers.HasFlag(ModifierKeys.Shift))
                {
                    FocusManager.SetFocusedElement(Parent, element);
                }
                else if (e.ChangedButton == MouseButton.Left
                    && Keyboard.Modifiers.HasFlag(ModifierKeys.Control)
                    && GetIsSelected(element))
                {
                    SelectedItems.Remove(element);
                    if (SelectedItems.Count == 0)
                    {
                        FocusManager.SetFocusedElement(
                            Parent,
                            Parent as UIElement);
                    }
                    else if (FocusManager.GetFocusedElement(Parent) == element)
                    {
                        var lastSelectedElement = SelectedItems
                            .Cast<UIElement>()
                            .Last();
                        FocusManager.SetFocusedElement(
                            Parent,
                            lastSelectedElement);
                    }

                    e.Handled = true;
                }
                else
                {
                    if (FocusManager.GetFocusedElement(Parent) == element)
                    {
                        SelectedItems.Clear();
                        SelectedItems.Add(element);
                    }
                    else
                    {
                        FocusManager.SetFocusedElement(Parent, element);
                    }
                }
            }
        }
    }
}
